﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using IPC.Communication;
using IPC.Communication.BasicFramework;
using IPC.Communication.Core;
using IPC.Communication.Core.IMessage;
using IPC.Communication.Reflection;

namespace IPC.Communication.Core.Net
{
    public class NetworkDoubleBase : NetworkBase, IDisposable
    {
	    private IByteTransform byteTransform;

	    private string ipAddress = "127.0.0.1";

	    private int port = 10000;

	    private int connectTimeOut = 10000;

	    private string connectionId = string.Empty;

	    private bool isUseSpecifiedSocket = false;

	    protected int receiveTimeOut = 5000;

	    protected bool isPersistentConn = false;

	    protected SimpleHybirdLock InteractiveLock;

	    protected bool IsSocketError = false;

	    protected bool LogMsgFormatBinary = true;

	    protected bool isUseAccountCertificate = false;

	    private string userName = string.Empty;

	    private string password = string.Empty;

	    private bool disposedValue = false;

	    private Lazy<Ping> ping = new Lazy<Ping>(() => new Ping());

	    public IByteTransform ByteTransform
	    {
		    get
		    {
			    return byteTransform;
		    }
		    set
		    {
			    byteTransform = value;
		    }
	    }

	    [HslMqttApi(HttpMethod = "GET", Description = "Gets or sets the timeout for the connection, in milliseconds")]
	    public virtual int ConnectTimeOut
	    {
		    get
		    {
			    return connectTimeOut;
		    }
		    set
		    {
			    if (value >= 0)
			    {
				    connectTimeOut = value;
			    }
		    }
	    }

	    [HslMqttApi(HttpMethod = "GET", Description = "Gets or sets the time to receive server feedback, and if it is a negative number, does not receive feedback")]
	    public int ReceiveTimeOut
	    {
		    get
		    {
			    return receiveTimeOut;
		    }
		    set
		    {
			    receiveTimeOut = value;
		    }
	    }

	    [HslMqttApi(HttpMethod = "GET", Description = "Get or set the IP address of the remote server. If it is a local test, then it needs to be set to 127.0.0.1")]
	    public virtual string IpAddress
	    {
		    get
		    {
			    return ipAddress;
		    }
		    set
		    {
			    ipAddress = HslHelper.GetIpAddressFromInput(value);
		    }
	    }

	    [HslMqttApi(HttpMethod = "GET", Description = "Gets or sets the port number of the server. The specific value depends on the configuration of the other party.")]
	    public virtual int Port
	    {
		    get
		    {
			    return port;
		    }
		    set
		    {
			    port = value;
		    }
	    }

	    [HslMqttApi(HttpMethod = "GET", Description = "The unique ID number of the current connection. The default is a 20-digit guid code plus a random number.")]
	    public string ConnectionId
	    {
		    get
		    {
			    return connectionId;
		    }
		    set
		    {
			    connectionId = value;
		    }
	    }

	    [HslMqttApi(HttpMethod = "GET", Description = "Get or set the time required to rest before officially receiving the data from the other party. When it is set to 0, no rest is required.")]
	    public int SleepTime
	    {
		    get;
		    set;
	    }

	    public IPEndPoint LocalBinding
	    {
		    get;
		    set;
	    }

	    public AlienSession AlienSession
	    {
		    get;
		    set;
	    }

	    public NetworkDoubleBase()
	    {
		    InteractiveLock = new SimpleHybirdLock();
		    connectionId = SoftBasic.GetUniqueStringByGuidAndRandom();
	    }

	    protected virtual INetMessage GetNewNetMessage()
	    {
		    return null;
	    }

	    public void SetPersistentConnection()
	    {
		    isPersistentConn = true;
	    }

	    public IPStatus IpAddressPing()
	    {
		    return ping.Value.Send(IpAddress).Status;
	    }

	    public OperateResult ConnectServer()
	    {
		    isPersistentConn = true;
            if (CoreSocket != null)
            {
                CoreSocket.Close();
            }
		    OperateResult<Socket> operateResult = CreateSocketAndInitialication();
		    if (!operateResult.IsSuccess)
		    {
			    IsSocketError = true;
			    operateResult.Content = null;
			    return operateResult;
		    }
		    CoreSocket = operateResult.Content;
            if (base.LogNet != null)
            {
                base.LogNet.WriteDebug(ToString(), StringResources.Language.NetEngineStart);
            }
		    return operateResult;
	    }

	    public OperateResult ConnectServer(AlienSession session)
	    {
		    isPersistentConn = true;
		    isUseSpecifiedSocket = true;
		    if (session != null)
		    {
                if (AlienSession != null && AlienSession.Socket != null)
                {
                    AlienSession.Socket.Close();
                }
			    if (string.IsNullOrEmpty(ConnectionId))
			    {
				    ConnectionId = session.DTU;
			    }
			    if (ConnectionId == session.DTU)
			    {
				    if (session.IsStatusOk)
				    {
					    OperateResult operateResult = InitializationOnConnect(session.Socket);
					    if (operateResult.IsSuccess)
					    {
						    CoreSocket = session.Socket;
						    IsSocketError = !session.IsStatusOk;
						    AlienSession = session;
					    }
					    else
					    {
						    IsSocketError = true;
					    }
					    return operateResult;
				    }
				    return new OperateResult();
			    }
			    IsSocketError = true;
			    return new OperateResult();
		    }
		    IsSocketError = true;
		    return new OperateResult();
	    }

	    public OperateResult ConnectClose()
	    {
		    OperateResult operateResult = new OperateResult();
		    isPersistentConn = false;
		    InteractiveLock.Enter();
		    try
		    {
			    operateResult = ExtraOnDisconnect(CoreSocket);
                if (CoreSocket != null)
                {
                    CoreSocket.Close();
                }
			    CoreSocket = null;
			    InteractiveLock.Leave();
		    }
		    catch
		    {
			    InteractiveLock.Leave();
			    throw;
		    }
            if (base.LogNet != null)
            {
                base.LogNet.WriteDebug(ToString(), StringResources.Language.NetEngineClose);
            }
		    return operateResult;
	    }

	    protected virtual OperateResult InitializationOnConnect(Socket socket)
	    {
		    return OperateResult.CreateSuccessResult();
	    }

	    protected virtual OperateResult ExtraOnDisconnect(Socket socket)
	    {
		    return OperateResult.CreateSuccessResult();
	    }

	    protected virtual void ExtraAfterReadFromCoreServer(OperateResult read)
	    {
	    }

	    public void SetLoginAccount(string userName, string password)
	    {
		    if (!string.IsNullOrEmpty(userName.Trim()))
		    {
			    isUseAccountCertificate = true;
			    this.userName = userName;
			    this.password = password;
		    }
		    else
		    {
			    isUseAccountCertificate = false;
		    }
	    }

	    protected OperateResult AccountCertificate(Socket socket)
	    {
		    OperateResult operateResult = SendAccountAndCheckReceive(socket, 1, userName, password);
		    if (!operateResult.IsSuccess)
		    {
			    return operateResult;
		    }
		    OperateResult<int, string[]> operateResult2 = ReceiveStringArrayContentFromSocket(socket);
		    if (!operateResult2.IsSuccess)
		    {
			    return operateResult2;
		    }
		    if (operateResult2.Content1 == 0)
		    {
			    return new OperateResult(operateResult2.Content2[0]);
		    }
		    return OperateResult.CreateSuccessResult();
	    }

        //[AsyncStateMachine(typeof(<AccountCertificateAsync>d__56))]
        //[DebuggerStepThrough]
        //protected Task<OperateResult> AccountCertificateAsync(Socket socket)
        //{
        //    <AccountCertificateAsync>d__56 stateMachine = new <AccountCertificateAsync>d__56();
        //    stateMachine.<>t__builder = AsyncTaskMethodBuilder<OperateResult>.Create();
        //    stateMachine.<>4__this = this;
        //    stateMachine.socket = socket;
        //    stateMachine.<>1__state = -1;
        //    stateMachine.<>t__builder.Start(ref stateMachine);
        //    return stateMachine.<>t__builder.Task;
        //}

        //[AsyncStateMachine(typeof(<InitializationOnConnectAsync>d__57))]
        //[DebuggerStepThrough]
        //protected virtual Task<OperateResult> InitializationOnConnectAsync(Socket socket)
        //{
        //    <InitializationOnConnectAsync>d__57 stateMachine = new <InitializationOnConnectAsync>d__57();
        //    stateMachine.<>t__builder = AsyncTaskMethodBuilder<OperateResult>.Create();
        //    stateMachine.<>4__this = this;
        //    stateMachine.socket = socket;
        //    stateMachine.<>1__state = -1;
        //    stateMachine.<>t__builder.Start(ref stateMachine);
        //    return stateMachine.<>t__builder.Task;
        //}

        //[AsyncStateMachine(typeof(<ExtraOnDisconnectAsync>d__58))]
        //[DebuggerStepThrough]
        //protected virtual Task<OperateResult> ExtraOnDisconnectAsync(Socket socket)
        //{
        //    <ExtraOnDisconnectAsync>d__58 stateMachine = new <ExtraOnDisconnectAsync>d__58();
        //    stateMachine.<>t__builder = AsyncTaskMethodBuilder<OperateResult>.Create();
        //    stateMachine.<>4__this = this;
        //    stateMachine.socket = socket;
        //    stateMachine.<>1__state = -1;
        //    stateMachine.<>t__builder.Start(ref stateMachine);
        //    return stateMachine.<>t__builder.Task;
        //}

        //[AsyncStateMachine(typeof(<CreateSocketAndInitialicationAsync>d__59))]
        //[DebuggerStepThrough]
        //private Task<OperateResult<Socket>> CreateSocketAndInitialicationAsync()
        //{
        //    <CreateSocketAndInitialicationAsync>d__59 stateMachine = new <CreateSocketAndInitialicationAsync>d__59();
        //    stateMachine.<>t__builder = AsyncTaskMethodBuilder<OperateResult<Socket>>.Create();
        //    stateMachine.<>4__this = this;
        //    stateMachine.<>1__state = -1;
        //    stateMachine.<>t__builder.Start(ref stateMachine);
        //    return stateMachine.<>t__builder.Task;
        //}

        //[AsyncStateMachine(typeof(<GetAvailableSocketAsync>d__60))]
        //[DebuggerStepThrough]
        //protected Task<OperateResult<Socket>> GetAvailableSocketAsync()
        //{
        //    <GetAvailableSocketAsync>d__60 stateMachine = new <GetAvailableSocketAsync>d__60();
        //    stateMachine.<>t__builder = AsyncTaskMethodBuilder<OperateResult<Socket>>.Create();
        //    stateMachine.<>4__this = this;
        //    stateMachine.<>1__state = -1;
        //    stateMachine.<>t__builder.Start(ref stateMachine);
        //    return stateMachine.<>t__builder.Task;
        //}

        //[AsyncStateMachine(typeof(<ConnectServerAsync>d__61))]
        //[DebuggerStepThrough]
        //public Task<OperateResult> ConnectServerAsync()
        //{
        //    <ConnectServerAsync>d__61 stateMachine = new <ConnectServerAsync>d__61();
        //    stateMachine.<>t__builder = AsyncTaskMethodBuilder<OperateResult>.Create();
        //    stateMachine.<>4__this = this;
        //    stateMachine.<>1__state = -1;
        //    stateMachine.<>t__builder.Start(ref stateMachine);
        //    return stateMachine.<>t__builder.Task;
        //}

        //[AsyncStateMachine(typeof(<ConnectCloseAsync>d__62))]
        //[DebuggerStepThrough]
        //public Task<OperateResult> ConnectCloseAsync()
        //{
        //    <ConnectCloseAsync>d__62 stateMachine = new <ConnectCloseAsync>d__62();
        //    stateMachine.<>t__builder = AsyncTaskMethodBuilder<OperateResult>.Create();
        //    stateMachine.<>4__this = this;
        //    stateMachine.<>1__state = -1;
        //    stateMachine.<>t__builder.Start(ref stateMachine);
        //    return stateMachine.<>t__builder.Task;
        //}

        //[AsyncStateMachine(typeof(<ReadFromCoreServerAsync>d__63))]
        //[DebuggerStepThrough]
        //public virtual Task<OperateResult<byte[]>> ReadFromCoreServerAsync(Socket socket, byte[] send, bool hasResponseData = true, bool usePackAndUnpack = true)
        //{
        //    <ReadFromCoreServerAsync>d__63 stateMachine = new <ReadFromCoreServerAsync>d__63();
        //    stateMachine.<>t__builder = AsyncTaskMethodBuilder<OperateResult<byte[]>>.Create();
        //    stateMachine.<>4__this = this;
        //    stateMachine.socket = socket;
        //    stateMachine.send = send;
        //    stateMachine.hasResponseData = hasResponseData;
        //    stateMachine.usePackAndUnpack = usePackAndUnpack;
        //    stateMachine.<>1__state = -1;
        //    stateMachine.<>t__builder.Start(ref stateMachine);
        //    return stateMachine.<>t__builder.Task;
        //}

        //[AsyncStateMachine(typeof(<ReadFromCoreServerAsync>d__64))]
        //[DebuggerStepThrough]
        //public Task<OperateResult<byte[]>> ReadFromCoreServerAsync(byte[] send)
        //{
        //    <ReadFromCoreServerAsync>d__64 stateMachine = new <ReadFromCoreServerAsync>d__64();
        //    stateMachine.<>t__builder = AsyncTaskMethodBuilder<OperateResult<byte[]>>.Create();
        //    stateMachine.<>4__this = this;
        //    stateMachine.send = send;
        //    stateMachine.<>1__state = -1;
        //    stateMachine.<>t__builder.Start(ref stateMachine);
        //    return stateMachine.<>t__builder.Task;
        //}

        //[AsyncStateMachine(typeof(<ReadFromCoreServerAsync>d__65))]
        //[DebuggerStepThrough]
        //public Task<OperateResult<byte[]>> ReadFromCoreServerAsync(byte[] send, bool hasResponseData, bool usePackAndUnpack = true)
        //{
        //    <ReadFromCoreServerAsync>d__65 stateMachine = new <ReadFromCoreServerAsync>d__65();
        //    stateMachine.<>t__builder = AsyncTaskMethodBuilder<OperateResult<byte[]>>.Create();
        //    stateMachine.<>4__this = this;
        //    stateMachine.send = send;
        //    stateMachine.hasResponseData = hasResponseData;
        //    stateMachine.usePackAndUnpack = usePackAndUnpack;
        //    stateMachine.<>1__state = -1;
        //    stateMachine.<>t__builder.Start(ref stateMachine);
        //    return stateMachine.<>t__builder.Task;
        //}

	    protected virtual byte[] PackCommandWithHeader(byte[] command)
	    {
		    return command;
	    }

	    protected virtual OperateResult<byte[]> UnpackResponseContent(byte[] send, byte[] response)
	    {
		    return OperateResult.CreateSuccessResult(response);
	    }

	    protected OperateResult<Socket> GetAvailableSocket()
	    {
		    if (isPersistentConn)
		    {
			    if (isUseSpecifiedSocket)
			    {
				    if (IsSocketError)
				    {
					    return new OperateResult<Socket>(StringResources.Language.ConnectionIsNotAvailable);
				    }
				    return OperateResult.CreateSuccessResult(CoreSocket);
			    }
			    if (IsSocketError || CoreSocket == null)
			    {
				    OperateResult operateResult = ConnectServer();
				    if (!operateResult.IsSuccess)
				    {
					    IsSocketError = true;
					    return OperateResult.CreateFailedResult<Socket>(operateResult);
				    }
				    IsSocketError = false;
				    return OperateResult.CreateSuccessResult(CoreSocket);
			    }
			    return OperateResult.CreateSuccessResult(CoreSocket);
		    }
		    return CreateSocketAndInitialication();
	    }

	    private OperateResult<Socket> CreateSocketAndInitialication()
	    {
		    OperateResult<Socket> operateResult = CreateSocketAndConnect(new IPEndPoint(IPAddress.Parse(ipAddress), port), connectTimeOut, LocalBinding);
		    if (operateResult.IsSuccess)
		    {
			    OperateResult operateResult2 = InitializationOnConnect(operateResult.Content);
			    if (!operateResult2.IsSuccess)
			    {
                    if (operateResult.Content != null)
                    {
                        operateResult.Content.Close();
                    }
				    operateResult.IsSuccess = operateResult2.IsSuccess;
				    operateResult.CopyErrorFromOther(operateResult2);
			    }
		    }
		    return operateResult;
	    }

	    public virtual OperateResult<byte[]> ReadFromCoreServer(Socket socket, byte[] send, bool hasResponseData = true, bool usePackAndUnpack = true)
	    {
		    byte[] array = usePackAndUnpack ? PackCommandWithHeader(send) : send;
            if (base.LogNet != null)
            {
                base.LogNet.WriteDebug(ToString(), StringResources.Language.Send + " : " + (LogMsgFormatBinary ? array.ToHexString(' ') : Encoding.ASCII.GetString(array)));
            }
		    INetMessage newNetMessage = GetNewNetMessage();
		    if (newNetMessage != null)
		    {
			    newNetMessage.SendBytes = array;
		    }
		    OperateResult operateResult = Send(socket, array);
		    if (!operateResult.IsSuccess)
		    {
			    return OperateResult.CreateFailedResult<byte[]>(operateResult);
		    }
		    if (receiveTimeOut < 0)
		    {
			    return OperateResult.CreateSuccessResult(new byte[0]);
		    }
		    if (!hasResponseData)
		    {
			    return OperateResult.CreateSuccessResult(new byte[0]);
		    }
		    if (SleepTime > 0)
		    {
			    Thread.Sleep(SleepTime);
		    }
		    OperateResult<byte[]> operateResult2 = ReceiveByMessage(socket, receiveTimeOut, newNetMessage);
		    if (!operateResult2.IsSuccess)
		    {
			    return operateResult2;
		    }
            if (base.LogNet != null)
            {
                base.LogNet.WriteDebug(ToString(), StringResources.Language.Receive + " : " + (LogMsgFormatBinary ? operateResult2.Content.ToHexString(' ') : Encoding.ASCII.GetString(operateResult2.Content)));
            }
		    if (newNetMessage != null && !newNetMessage.CheckHeadBytesLegal(base.Token.ToByteArray()))
		    {
                if (socket != null)
                {
                    socket.Close();
                }
			    return new OperateResult<byte[]>(StringResources.Language.CommandHeadCodeCheckFailed + Environment.NewLine + StringResources.Language.Send + ": " + SoftBasic.ByteToHexString(array, ' ') + Environment.NewLine + StringResources.Language.Receive + ": " + SoftBasic.ByteToHexString(operateResult2.Content, ' '));
		    }
		    return usePackAndUnpack ? UnpackResponseContent(array, operateResult2.Content) : OperateResult.CreateSuccessResult(operateResult2.Content);
	    }

	    public OperateResult<byte[]> ReadFromCoreServer(byte[] send)
	    {
		    return ReadFromCoreServer(send, hasResponseData: true);
	    }

	    private OperateResult<byte[]> ReadFromCoreServer(byte[] send, bool hasResponseData, bool usePackAndUnpack = true)
	    {
		    OperateResult<byte[]> operateResult = new OperateResult<byte[]>();
		    OperateResult<Socket> operateResult2 = null;
		    InteractiveLock.Enter();
		    try
		    {
			    operateResult2 = GetAvailableSocket();
			    if (!operateResult2.IsSuccess)
			    {
				    IsSocketError = true;
                    if (AlienSession != null)
                    {
                        AlienSession.Offline();
                    }
				    InteractiveLock.Leave();
				    operateResult.CopyErrorFromOther(operateResult2);
				    return operateResult;
			    }
			    OperateResult<byte[]> operateResult3 = ReadFromCoreServer(operateResult2.Content, send, hasResponseData, usePackAndUnpack);
			    if (operateResult3.IsSuccess)
			    {
				    IsSocketError = false;
				    operateResult.IsSuccess = operateResult3.IsSuccess;
				    operateResult.Content = operateResult3.Content;
				    operateResult.Message = StringResources.Language.SuccessText;
			    }
			    else
			    {
				    IsSocketError = true;
                    if (AlienSession != null)
                    {
                        AlienSession.Offline();
                    }
				    operateResult.CopyErrorFromOther(operateResult3);
			    }
			    ExtraAfterReadFromCoreServer(operateResult3);
			    InteractiveLock.Leave();
		    }
		    catch
		    {
			    InteractiveLock.Leave();
			    throw;
		    }
		    if (!isPersistentConn && operateResult2 != null)
		    {
                if (operateResult2.Content != null)
                {
                    operateResult2.Content.Close();
                }
		    }
		    return operateResult;
	    }

	    protected virtual void Dispose(bool disposing)
	    {
		    if (!disposedValue)
		    {
			    if (disposing)
			    {
				    ConnectClose();
                    if (InteractiveLock != null)
                    {
                        InteractiveLock.Dispose();
                    }
			    }
			    disposedValue = true;
		    }
	    }

	    public void Dispose()
	    {
		    Dispose(disposing: true);
	    }

	    public override string ToString()
	    {
            return String.Format("NetworkDoubleBase<{0}, {1}>[{2}:{3}]", GetNewNetMessage().GetType(), ByteTransform.GetType(), IpAddress, Port);
	    }
    }
}
